/*
 * Copyright 2020 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/gpu/ganesh/d3d/GrD3DTexture.h"

#include "src/gpu/ganesh/GrTexture.h"
#include "src/gpu/ganesh/d3d/GrD3DGpu.h"
#include "src/gpu/ganesh/d3d/GrD3DUtil.h"

#include "include/gpu/d3d/GrD3DTypes.h"

// Because this class is virtually derived from GrSurface we must explicitly call its constructor.
GrD3DTexture::GrD3DTexture(GrD3DGpu *gpu, skgpu::Budgeted budgeted, SkISize dimensions,
    const GrD3DTextureResourceInfo &info, sk_sp<GrD3DResourceState> state,
    const GrD3DDescriptorHeap::CPUHandle &shaderResourceView, GrMipmapStatus mipmapStatus, std::string_view label)
    : GrSurface(gpu, dimensions, info.fProtected, label),
      GrD3DTextureResource(info, std::move(state)),
      INHERITED(gpu, dimensions, info.fProtected, GrTextureType::k2D, mipmapStatus, label),
      fShaderResourceView(shaderResourceView)
{
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == info.fLevelCount));
    this->registerWithCache(budgeted);
    if (GrDxgiFormatIsCompressed(info.fFormat)) {
        this->setReadOnly();
    }
}

GrD3DTexture::GrD3DTexture(GrD3DGpu *gpu, SkISize dimensions, const GrD3DTextureResourceInfo &info,
    sk_sp<GrD3DResourceState> state, const GrD3DDescriptorHeap::CPUHandle &shaderResourceView,
    GrMipmapStatus mipmapStatus, GrWrapCacheable cacheable, GrIOType ioType, std::string_view label)
    : GrSurface(gpu, dimensions, info.fProtected, label),
      GrD3DTextureResource(info, std::move(state)),
      INHERITED(gpu, dimensions, info.fProtected, GrTextureType::k2D, mipmapStatus, label),
      fShaderResourceView(shaderResourceView)
{
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == info.fLevelCount));
    if (ioType == kRead_GrIOType) {
        this->setReadOnly();
    }
    this->registerWithCacheWrapped(cacheable);
}

// Because this class is virtually derived from GrSurface we must explicitly call its constructor.
GrD3DTexture::GrD3DTexture(GrD3DGpu *gpu, SkISize dimensions, const GrD3DTextureResourceInfo &info,
    sk_sp<GrD3DResourceState> state, const GrD3DDescriptorHeap::CPUHandle &shaderResourceView,
    GrMipmapStatus mipmapStatus, std::string_view label)
    : GrSurface(gpu, dimensions, info.fProtected, label),
      GrD3DTextureResource(info, state),
      INHERITED(gpu, dimensions, info.fProtected, GrTextureType::k2D, mipmapStatus, label),
      fShaderResourceView(shaderResourceView)
{
    SkASSERT((GrMipmapStatus::kNotAllocated == mipmapStatus) == (1 == info.fLevelCount));
}

sk_sp<GrD3DTexture> GrD3DTexture::MakeNewTexture(GrD3DGpu *gpu, skgpu::Budgeted budgeted, SkISize dimensions,
    const D3D12_RESOURCE_DESC &desc, GrProtected isProtected, GrMipmapStatus mipmapStatus, std::string_view label)
{
    GrD3DTextureResourceInfo info;
    if (!GrD3DTextureResource::InitTextureResourceInfo(gpu, desc, D3D12_RESOURCE_STATE_COPY_DEST, isProtected, nullptr,
        &info)) {
        return nullptr;
    }

    sk_sp<GrD3DResourceState> state(new GrD3DResourceState(static_cast<D3D12_RESOURCE_STATES>(info.fResourceState)));

    GrD3DDescriptorHeap::CPUHandle shaderResourceView =
        gpu->resourceProvider().createShaderResourceView(info.fResource.get());

    GrD3DTexture *tex =
        new GrD3DTexture(gpu, budgeted, dimensions, info, std::move(state), shaderResourceView, mipmapStatus, label);

    return sk_sp<GrD3DTexture>(tex);
}

sk_sp<GrD3DTexture> GrD3DTexture::MakeWrappedTexture(GrD3DGpu *gpu, SkISize dimensions, GrWrapCacheable cacheable,
    GrIOType ioType, const GrD3DTextureResourceInfo &info, sk_sp<GrD3DResourceState> state)
{
    // TODO: If a client uses their own heap to allocate, how do we manage that?
    // Adopted textures require both image and allocation because we're responsible for freeing
    // SkASSERT(info.fTexture &&
    //         (kBorrow_GrWrapOwnership == wrapOwnership || VK_NULL_HANDLE != info.fAlloc.fMemory));

    GrMipmapStatus mipmapStatus = info.fLevelCount > 1 ? GrMipmapStatus::kValid : GrMipmapStatus::kNotAllocated;

    GrD3DDescriptorHeap::CPUHandle shaderResourceView =
        gpu->resourceProvider().createShaderResourceView(info.fResource.get());

    return sk_sp<GrD3DTexture>(new GrD3DTexture(gpu, dimensions, info, std::move(state), shaderResourceView,
        mipmapStatus, cacheable, ioType,
        /* label= */ "D3DWrappedTexture"));
}

sk_sp<GrD3DTexture> GrD3DTexture::MakeAliasingTexture(GrD3DGpu *gpu, sk_sp<GrD3DTexture> originalTexture,
    const D3D12_RESOURCE_DESC &newDesc, D3D12_RESOURCE_STATES resourceState)
{
    GrD3DTextureResourceInfo info = originalTexture->fInfo;
    info.fResource = gpu->memoryAllocator()->createAliasingResource(info.fAlloc, 0, &newDesc, resourceState, nullptr);
    if (!info.fResource) {
        return nullptr;
    }
    info.fResourceState = resourceState;

    sk_sp<GrD3DResourceState> state(new GrD3DResourceState(static_cast<D3D12_RESOURCE_STATES>(resourceState)));

    GrD3DDescriptorHeap::CPUHandle shaderResourceView =
        gpu->resourceProvider().createShaderResourceView(info.fResource.get());

    GrD3DTexture *tex = new GrD3DTexture(gpu, skgpu::Budgeted::kNo, originalTexture->dimensions(), info,
        std::move(state), shaderResourceView, originalTexture->mipmapStatus(),
        /* label= */ "AliasingTexture");
    return sk_sp<GrD3DTexture>(tex);
}

void GrD3DTexture::onRelease()
{
    GrD3DGpu *gpu = this->getD3DGpu();
    gpu->resourceProvider().recycleShaderView(fShaderResourceView);
    this->releaseResource(gpu);

    INHERITED::onRelease();
}

void GrD3DTexture::onAbandon()
{
    GrD3DGpu *gpu = this->getD3DGpu();
    gpu->resourceProvider().recycleShaderView(fShaderResourceView);
    this->releaseResource(gpu);
    INHERITED::onAbandon();
}

GrBackendTexture GrD3DTexture::getBackendTexture() const
{
    return GrBackendTexture(this->width(), this->height(), fInfo, this->grD3DResourceState());
}

GrD3DGpu *GrD3DTexture::getD3DGpu() const
{
    SkASSERT(!this->wasDestroyed());
    return static_cast<GrD3DGpu *>(this->getGpu());
}

void GrD3DTexture::onSetLabel()
{
    SkASSERT(this->d3dResource());
    if (!this->getLabel().empty()) {
        const std::wstring label = L"_Skia_" + GrD3DMultiByteToWide(this->getLabel());
        this->d3dResource()->SetName(label.c_str());
    }
}
